﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using SunwellRenderer.Camera;
using Microsoft.Xna.Framework.Input;

using Librarinth;

namespace SunwellRenderer.Rendering
{
	public struct ShaderData
	{
		public ShaderEffect Shader;
		public BufferArray<int> ModelBuffer;
	}
	public struct ModelData
	{
		public Model Model;
		public int MaterialIndex,MaterialCount;
	}

	public struct ShaderMaterialPair
	{
		public readonly int ShaderIndex;
		public readonly int ShaderDataIndex;
		public ShaderMaterialPair(int shaderIndex,int shaderDataIndex)
		{
			ShaderIndex	= shaderIndex;
			ShaderDataIndex	= shaderDataIndex;
		}
	}

	public struct RenderingData
	{
		public List<ShaderMaterialPair> AllPair;
	}
	public class RenderingModule : Module<RenderingData>
	{
		public readonly Locker<string,ModelData> AllModels	= new Locker<string,ModelData>();
		public readonly Locker<string,ShaderData> AllShaders	= new Locker<string,ShaderData>();
		public readonly Locker<string,Texture2D> AllGlobalTexture	= new Locker<string,Texture2D>();

		void AddShader<T>(out int index) where T : ShaderEffect,new()
		{
			AllShaders.GetIndex(typeof(T).Name,out index);
			AllShaders.Datas[index].Shader	= new T();
		}

		void LoadModel(string assetName,out int i)
		{
			AllModels.GetIndex(assetName,out i);
			AllModels.Datas[i].Model	= SunwellMain.Instance.Content.Load<Model>(assetName);

			int lastIndex	= 0;
			AllModels.Datas[i].MaterialCount	= 0;
			foreach(var mesh in AllModels.Datas[i].Model.Meshes)
			{
				DataPool<BasicMaterial>.Alloc(out lastIndex);

				BasicMaterial.FromMesh(mesh,0,out DataPool<BasicMaterial>.Datas[lastIndex]);

				AllModels.Datas[i].MaterialCount++;
			}

			if(AllModels.Datas[i].MaterialCount > 0)
				AllModels.Datas[i].MaterialIndex	= lastIndex - AllModels.Datas[i].MaterialCount + 1;
		}

		internal readonly PerspectiveCamera CameraModule	= new PerspectiveCamera();
		StaticShadowRenderer shadowRenderer;
		GBufferRenderer gBufferRenderer;
		CombineRenderer combineRenderer;
		GraphicsDevice graphicsDevice;
		public void LoadContent()
		{
			shadowRenderer	= new StaticShadowRenderer();
			gBufferRenderer	= new GBufferRenderer();
			combineRenderer	= new CombineRenderer();

			graphicsDevice	= SunwellMain.Instance.GraphicsDevice;

			{
				shadowRenderer.LoadContent();
				gBufferRenderer.LoadContent();
				combineRenderer.LoadContent();
			}

			{
				int i;
				AddShader<BetaShader>(out i);
				AddShader<LeafShader>(out i);
				AddShader<ShadowShader>(out i);
				AddShader<CombineShader>(out i);
			}

		}

		public int CameraIndex;
		public void AddCamera(int objectIndex)
		{
			CameraModule.Alloc(out CameraIndex);
			CameraModule.Datas[CameraIndex]	= new CameraData(SunwellMain.Instance.Simulation.Datas[objectIndex].SpatialIndex
				,MathHelper.PiOver4,graphicsDevice.Viewport.Width / (float)graphicsDevice.Viewport.Height,0.1f,1000f);
		}

		int lightCameraIndex;
		public void AddDirectionalLight(int objectIndex,string shaderName,Vector3 color)
		{
			CameraModule.Alloc(out lightCameraIndex);
			CameraModule.Datas[lightCameraIndex]	= new CameraData(SunwellMain.Instance.Simulation.Datas[objectIndex].SpatialIndex
				,MathHelper.PiOver4,graphicsDevice.Viewport.Width / (float)graphicsDevice.Viewport.Height,0.1f,1000f);

			int shaderIndex;
			AllShaders.GetIndex(shaderName,out shaderIndex);
			var shader	= AllShaders.Datas[shaderIndex].Shader;

			int i;
			shader.Alloc(out i);
			shader.Datas[i].OBJIndex	= objectIndex;

			DataPool<DirLightData>.Alloc(out i);
			shader.Datas[i].MeshIndex	= i;
			DataPool<DirLightData>.Datas[i].Color	= new Vector3(2,2,2);

			var SH	= new SphericalHarmonicL2RGB();
			{
				SH.AddLight(Color.SkyBlue.ToVector3(),new Vector3(-1,1,-1),0.5f);
				SH.AddLight(Color.SkyBlue.ToVector3(),new Vector3(1,-1,1),0.5f);
				SH.AddLight(Color.LightYellow.ToVector3(),new Vector3(1,1,1),0.5f);
				SH.AddLight(Color.SkyBlue.ToVector3(),new Vector3(-1,-1,-1),0.5f);
				SH.AddLight(Color.LightYellow.ToVector3(),new Vector3(1,1,-1),0.5f);
				SH.AddLight(Color.SkyBlue.ToVector3(),new Vector3(-1,1,1),0.5f);
				SH.AddLight(Color.LightYellow.ToVector3(),new Vector3(1,-1,-1),0.5f);
				SH.AddLight(Color.SkyBlue.ToVector3(),new Vector3(-1,-1,1),0.5f);
			}

			SH.CopyToGpuArray(ref DataPool<DirLightData>.Datas[i].SHLight);
		}
		public void AddMeshToShader(int objectIndex,string shaderName,string modelName,int meshIndex)
		{
			int shaderIndex;
			AllShaders.GetIndex(shaderName,out shaderIndex);
			var shader	= AllShaders.Datas[shaderIndex].Shader;

			int i;
			shader.Alloc(out i);
			shader.Datas[i].OBJIndex	= objectIndex;
			shader.Datas[i].MeshIndex	= meshIndex;

			LoadModel(modelName,out shader.Datas[i].ModelIndex);

			int modelIndex	=  shader.Datas[i].ModelIndex;
			var mesh	= AllModels.Datas[modelIndex].Model.Meshes[meshIndex];
			shader.Datas[i].MaterialIndex	= meshIndex + AllModels.Datas[modelIndex].MaterialIndex;
			shader.Datas[i].Bounding	= mesh.BoundingSphere;

			var renderIndex	= SunwellMain.Instance.Simulation.Datas[objectIndex].RenderIndex;

			Datas[renderIndex].AllPair.Add(new ShaderMaterialPair(shaderIndex,i));
		}

		public void ShaderListReport()
		{
			int shindex = 0;
			foreach(var shaderIndex in AllShaders.AllIndex)
			{
				foreach(var meshIndex in AllShaders.Datas[shaderIndex].Shader.AllIndex)
				{
					var sH	= AllShaders.Datas[shaderIndex].Shader.Datas[meshIndex];

					Console.WriteLine("------ shader " + shindex + " ------");
					Console.WriteLine(" Model " + sH.ModelIndex +" Mesh "
						+ sH.MeshIndex + " Shader " + AllShaders.Datas[shaderIndex].Shader.ToString());
					shindex++;
				}
			}
		}

		/*
				private FirstPersonCamera shadowCamera;
				public FirstPersonCamera CameraMain;

				private void updateShadowMap(int index)
				{
					shadowCamera.Position = CameraMain.Position;
					shadowCamera.XRotation = CameraMain.XRotation;
					shadowCamera.YRotation = CameraMain.YRotation;
					staticShadowRenderer.Update(-Light.Direction,shadowCamera.ViewProjectionMatrix);
				}

				public void DrawShadowDepth(int index)
				{
					updateShadowMap(index);
					Shadowmap.Begin(index);

					var spatialDatas	= SunwellMain.SpatialModule.Datas;
					var renderingDatas	= SunwellMain.RenderingModule.Datas;
					foreach(var shader in shaderList)
					{
						foreach(var shaderobj in shader.Datas)
						{
							if(!shaderobj.ShadowCollect)
								continue;

							var obj	= SunwellMain.Simulation.Datas[shaderobj.IndexOBJ];
							if(!renderingDatas[obj.RenderingIndex].Active)
								continue;


							float distancemodel = Vector3.Distance(spatialDatas[obj.SpatialIndex].World.Translation,CameraMain.Position);
							for(int l = 0;l < lodLevel.Length;l++)
							{
								if(distancemodel > lodLevel[l])
									renderingDatas[obj.RenderingIndex].LOD	= LODDictionary[lodLevel[l]];
							}

							var LOD	= renderingDatas[obj.RenderingIndex].LOD;
							var modelIndex	= shaderobj.IndexModel;
							if(LOD >= ObjManager.ModelsInstance[modelIndex].Models.Length)
								continue;

							if(LOD != shader.LODIndexUse)
								continue;


							ModelMesh mesh = ObjManager.ModelsInstance[shaderobj.IndexModel].Models[0].Meshes[shaderobj.IndexMesh];
							BoundingSphere bound;
							ObjManager.ModelsInstance[modelIndex].Bounding.Transform(ref spatialDatas[obj.SpatialIndex].World,out bound);

							if((distancemodel < shadowCamera[index].FarClip && distancemodel < shadowCamera[index].FarClip) || distancemodel < shadowCamera[index].FarClip + 30)
							{
								shadowDepth.CurrentTechnique	= shadowDepth.Techniques["CreateShadowMap"];
								shadowDepth.Parameters["Texture"].SetValue(ObjManager.ModelsInstance[modelIndex].TextureMeshs[shaderobj.IndexMesh].Textures[0]);
								shadowDepth.Parameters["World"].SetValue(spatialDatas[obj.SpatialIndex].World);
								shadowDepth.Parameters["lightViewProjection"].SetValue(Shadowmap.LightViewProjectionMatrix[index]);
								foreach(ModelMeshPart parts in mesh.MeshParts)
								{
									parts.Effect = shadowDepth;
								}
								mesh.Draw();
							}
						}
					}
					Shadowmap.End(index);
				}
		*/

		public int MeshCounter;
		public void DrawMesh(GraphicsDevice device,Effect effect,ModelMesh mesh)
		{
			MeshCounter += 1;

			device.Indices	= mesh.IndexBuffer;
			foreach(ModelMeshPart part in mesh.MeshParts)
			{
				device.VertexDeclaration	= part.VertexDeclaration;
				device.Vertices[0].SetSource(mesh.VertexBuffer,part.StreamOffset,part.VertexStride);

				foreach(var pass in effect.CurrentTechnique.Passes)
				{
					pass.Begin();

					device.DrawIndexedPrimitives(PrimitiveType.TriangleList,part.BaseVertex,0
						,part.NumVertices,part.StartIndex,part.PrimitiveCount);

					pass.End();
				}
			}
		}

		bool PassFilter(ref BoundingFrustum Frustum,ref ModelMeshData modelData)
		{
			BoundingSphere bound;
			var obj	= SunwellMain.Instance.Simulation.Datas[modelData.OBJIndex];
			modelData.Bounding.Transform(ref SunwellMain.Instance.SpatialModule.Datas[obj.SpatialIndex].World,out bound);

			return Frustum.Contains(bound) != ContainmentType.Disjoint;
		}
		void Filter(Renderer renderer)
		{
			var spatialDatas	= SunwellMain.Instance.SpatialModule.Datas;

			var Frustum	= new BoundingFrustum(CameraModule.ViewProjection(CameraIndex));
			foreach(var rendererIndex in renderer.AllIndex)
			{
				int shaderIndex	= renderer.Datas[rendererIndex].ShaderIndex;
				var shader	= AllShaders.Datas[shaderIndex].Shader;
				foreach(var j in shader.AllIndex)
				{
					if(PassFilter(ref Frustum,ref shader.Datas[j]))
						AllShaders.Datas[shaderIndex].ModelBuffer.Add(j);
				}
			}
		}
		bool PassShadowFilter(Vector3 cameraPos,ref BoundingFrustum Frustum,ref ModelMeshData modelData)
		{
			var obj	= SunwellMain.Instance.Simulation.Datas[modelData.OBJIndex];
			var spatialDatas = SunwellMain.Instance.SpatialModule.Datas;

			BoundingSphere bound;
			modelData.Bounding.Transform(ref spatialDatas[obj.SpatialIndex].World,out bound);

			float dst	= Vector3.Distance(cameraPos,spatialDatas[obj.SpatialIndex].World.Translation);
			return dst < 500 || Frustum.Contains(bound) != ContainmentType.Disjoint;
		}
		void FilterShadow(Renderer renderer)
		{
			var spatialDatas = SunwellMain.Instance.SpatialModule.Datas;

			var cameraPos	= CameraModule.CameraWorld(CameraIndex).Translation;
			var Frustum = new BoundingFrustum(CameraModule.ViewProjection(CameraIndex));
			foreach(var rendererIndex in renderer.AllIndex)
			{
				int shaderIndex = renderer.Datas[rendererIndex].ShaderIndex;
				var shader = AllShaders.Datas[shaderIndex].Shader;
				foreach(var j in shader.AllIndex)
				{
					if(PassShadowFilter(cameraPos,ref Frustum,ref shader.Datas[j]))
						AllShaders.Datas[shaderIndex].ModelBuffer.Add(j);
				}
			}
		}
		void FilterLight(Renderer renderer)
		{
			foreach(var rendererIndex in renderer.AllIndex)
			{
				int shaderIndex = renderer.Datas[rendererIndex].ShaderIndex;
				var shader = AllShaders.Datas[shaderIndex].Shader;
				foreach(var j in shader.AllIndex)
				{
					AllShaders.Datas[shaderIndex].ModelBuffer.Add(j);
				}
			}
		}


		public void Draw()
		{
			FilterShadow(shadowRenderer);
			shadowRenderer.Draw(graphicsDevice,lightCameraIndex);

			MeshCounter = 0;

			Filter(gBufferRenderer);
			gBufferRenderer.Draw(graphicsDevice,CameraIndex);

			FilterLight(combineRenderer);
			combineRenderer.Draw(graphicsDevice,CameraIndex);

			foreach(int i in AllShaders.AllIndex)
				AllShaders.Datas[i].ModelBuffer.Clear();
		}

		protected override void Alloc(ref RenderingData data)
		{ data.AllPair	= new List<ShaderMaterialPair>(); }

		protected override void Free(ref RenderingData data)
		{ data.AllPair	= null; }
	}
}